Tree Shaking 不是魔法,是有代价的
提示本文发布于 5 个月前,其中信息可能已经时过境迁...
以前我一直觉得 Tree Shaking 是打包器自动做的事,写代码的时候不太需要考虑。直到有一次用 bundle analyzer 看打包结果,发现一个只用了两个方法的工具库,竟然把整个文件都打包进去了。
查了一圈才明白,Tree Shaking 不是魔法,它有很多前提条件——写不对,它就摇不动。
核心原理:静态分析
Tree Shaking 依赖 ESM 的静态结构。import { add } from './math' 这种写法,打包器在编译时就能确定你用了什么、没用什么。
但 CommonJS 就不行:
javascript
// ❌ 动态的,摇不动
const math = require('./math');
const { add } = math;
// ✅ 静态的,可以摇
import { add } from './math';几个常见陷阱
1. 默认导出对象
javascript
// ❌ 导出一个对象,里面的方法全都会被打包
export default {
add: (a, b) => a + b,
multiply: (a, b) => a * b,
};
// ✅ 命名导出,只打包用到的
export const add = (a, b) => a + b;
export const multiply = (a, b) => a * b;2. 有副作用的代码
如果函数里有 console.log,打包器会认为它有副作用,不敢移除:
javascript
// 即使没人调用 multiply,它也会被打包
export function multiply(a, b) {
console.log('multiply called');
return a * b;
}可以通过 /*#__PURE__*/ 注释告诉打包器这是安全的:
javascript
export function multiply(a, b) {
/*#__PURE__*/ console.log('multiply called');
return a * b;
}3. import * 的问题
javascript
// ❌ 整个模块都会被打包
import * as utils from './utils';
utils.add(1, 2);
// ✅ 只打包 add
import { add } from './utils';第三方库怎么选
- lodash → 用
lodash-es或者按路径导入import debounce from 'lodash/debounce' - moment → 换成
date-fns,天然支持 Tree Shaking - UI 组件库 → 确认是否支持按需导入
调试打包体积
用 webpack-bundle-analyzer 或者 Vite 的 rollup-plugin-visualizer,能直观看到每个模块的大小和引用关系。我每次重构完都会跑一遍,看看有没有意外打包进去的东西。
Tree Shaking 这件事,说到底就是:写好 ESM、避免副作用、选对库版本。不是什么高深的技术,但忽略了它,打包体积就会不知不觉地涨上去。
